/**
*Createed by yellowTT's game
*FileName:     CameraController.cs
*Author:       yellowTT
*UnityVersion：5.6.0f3
*Date:         2019年02月22日,Friday, 00时28分15秒
*/

/***************************************
*FileName:     CameraController.cs
*Author:       yellowTT
*UnityVersion：5.6.0f3
*Date:         2019年02月22日,Friday, 00时28分15秒
***************************************/

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
 * 相机功能基类  提供基本的移动旋转等功能
 */
public class CameraController : MonoBehaviour
{
    public static CameraController Instance { get; private set; }

    public float targetYaw;
    public float targetPitch;
    public float targetDistance;

    public float currentYaw;
    public float currentPitch;
    public float currentDistance;
    public float currentMinDistance;
    public float currentMaxDistance;

    public float realDistance = 0f;

    public bool DisplayDebugGizmos = true;

    public UnityEngine.Camera Camera = null;
    public Transform Target = null;

#if PROJECTX_GAME
        public XPlayer Player = null;
#elif TPC_PROTO
        public ProjectX.Player.Player Player = null;
#endif

    public float MinDistance = 1f;
    public float MaxDistance = 32f;
    public float MinPitch = -80f;
    public float MaxPitch = 80f;
    public float ZoomSpeed = 16f;
    public float RotationMouseSpeed = 4f;

    public bool SmoothZoom = true;
    public float SmoothZoomSpeed = 8f;

    public bool SmoothRotation = true;
    public float SmoothRotationSpeed = 8f;

    public bool SmoothAutoRotation = true;
    public float SmoothAutoRotationSpeed = 4f;

    public bool UseRotateBehindTarget = false;

    public LayerMask Obstacles = 0;
    public Vector3 TargetOffset = Vector3.zero;

    public string ZoomAxis = "Mouse ScrollWheel";
    public string YawAxis = "Mouse X";
    public string PitchAxis = "Mouse Y";
    public string MouseRotateButton = "Fire1";
    public string MouseLookButton = "Fire2";

    public bool inputAllowed = true;

    public Vector3 offset;
    public float collisionClosest = float.MaxValue;

    public bool LockCameraBehindTarget { get; set; }
    public bool RotateCameraBehindTarget { get; set; }

    public bool HasCamera { get { return Camera != null; } }
    public bool HasTarget { get { return Target != null; } }

    public Vector3 _TargetPosition;
    public Vector3 TargetPosition
    {
        get
        {
            _TargetPosition = HasTarget ? Target.position + TargetOffset : TargetOffset;
            return _TargetPosition;
        }
    }


    void Start()
    {
        Instance = this;

        if (!HasCamera)
        {
            Camera = GetComponentInChildren<UnityEngine.Camera>();
        }

        if (!HasTarget)
        {
            try
            {
                Target = GameObject.FindGameObjectWithTag("CameraTarget").transform;
            }
            catch
            {

            }
        }

        if (HasCamera)
        {
            Camera.transform.localPosition = Vector3.zero;
            Camera.transform.localRotation = Quaternion.identity;
        }

        MinPitch = Mathf.Clamp(MinPitch, -85f, 0f);
        MaxPitch = Mathf.Clamp(MaxPitch, 0f, 85f);
        MinDistance = Mathf.Max(0f, MinDistance);

        currentMinDistance = MinDistance;
        currentMaxDistance = MaxDistance;

        Reset();
    }


    public void Reset()
    {
        currentYaw = targetYaw = 0f;
        currentPitch = targetPitch = Mathf.Lerp(MinPitch, MaxPitch, 0.6f);
        currentDistance = targetDistance = realDistance = Mathf.Lerp(MinDistance, MaxDistance, 0.5f);
    
        {
            offset = new Vector3(0, 0, -currentDistance);

            targetPitch -= (Input.GetAxisRaw(PitchAxis) * RotationMouseSpeed);
            targetPitch = Mathf.Clamp(targetPitch, MinPitch, MaxPitch);

            targetYaw = SignedAngle(offset.normalized, -Target.transform.forward, Vector3.up);

            currentYaw = targetYaw;
            currentPitch = targetPitch;

            UpdateMovement();

            UpdateCollision();
        }
    }


    void LateUpdate()
    {
        Instance = this;

        if (!inputAllowed)
        {
            return;
        }

        if (!HasCamera)
        {
            //Debug.LogError("No camera found");
            return;
        }

        if (!HasTarget)
        {
            //Debug.LogError("No target found");
            return;
        }

        bool rotate = Input.GetButton(MouseRotateButton);
        bool mouseLook = Input.GetButton(MouseLookButton);

        bool smoothRotation = SmoothRotation || SmoothAutoRotation;
        float smoothRotationSpeed = SmoothRotationSpeed;

        LockCameraBehindTarget = false;

#if PROJECTX_GAME || TPC_PROTO
            if (Player != null && Player.IsMoving && UseRotateBehindTarget)
            {
                RotateCameraBehindTarget = true;
                rotate = false;
            }
            else
            {
                RotateCameraBehindTarget = false;
                rotate = true;
            }
#else
        RotateCameraBehindTarget = false;
        rotate = true;
#endif

        // This defines our "real" distance to the player
        realDistance -= Input.GetAxisRaw(ZoomAxis) * ZoomSpeed;
        realDistance = Mathf.Clamp(realDistance, MinDistance, MaxDistance);

        // This is the distance we want to (clamped to what is viewable)
        targetDistance = realDistance;
        targetDistance = Mathf.Clamp(targetDistance, currentMinDistance, currentMaxDistance);

        // This is our current distance
        if (SmoothZoom)
        {
            currentDistance = Mathf.Lerp(currentDistance, targetDistance, Time.deltaTime * SmoothZoomSpeed);
        }
        else
        {
            currentDistance = targetDistance;
        }

        // Calculate offset vector
        offset = new Vector3(0, 0, -currentDistance);

        // LMB is not down, but we should rotate camera behind target
        if (!rotate && RotateCameraBehindTarget)
        {
            targetYaw = SignedAngle(offset.normalized, -Target.forward, Vector3.up);
            smoothRotation = SmoothAutoRotation;
            smoothRotationSpeed = SmoothAutoRotationSpeed;
        }

        // Only LMB down and no lock
        if (rotate && !mouseLook && !LockCameraBehindTarget)
        {
            targetYaw += (Input.GetAxisRaw(YawAxis) * RotationMouseSpeed);
            targetPitch -= (Input.GetAxisRaw(PitchAxis) * RotationMouseSpeed);
            targetPitch = Mathf.Clamp(targetPitch, MinPitch, MaxPitch);
            smoothRotation = SmoothRotation;
            smoothRotationSpeed = SmoothRotationSpeed;
        }

#if PROJECTX_GAME || TPC_PROTO
            if (Player != null && Player.IsMoving)
            {
                targetPitch -= (Input.GetAxisRaw(PitchAxis) * RotationMouseSpeed);
                targetPitch = Mathf.Clamp(targetPitch, MinPitch, MaxPitch);
            }
#endif

        // RMB 
        if (mouseLook && LockCameraBehindTarget)
        {
            targetPitch -= (Input.GetAxisRaw(PitchAxis) * RotationMouseSpeed);
            targetPitch = Mathf.Clamp(targetPitch, MinPitch, MaxPitch);
        }

        // Lock camera behind target, this overrides everything
        if (LockCameraBehindTarget)
        {
            targetYaw = SignedAngle(offset.normalized, -Target.transform.forward, Vector3.up);
            smoothRotation = false;
        }

        // Clamp targetYaw to -180, 180
        targetYaw = Mathf.Repeat(targetYaw + 180f, 360f) - 180f;

        if (!smoothRotation)
        {
            currentYaw = targetYaw;
            currentPitch = targetPitch;
        }
        else
        {
            // Clamp smooth currentYaw to targetYaw and clamp it to -180, 180
            currentYaw = Mathf.LerpAngle(currentYaw, targetYaw, Time.deltaTime * smoothRotationSpeed);
            currentYaw = Mathf.Repeat(currentYaw + 180f, 360f) - 180f;

            // Smooth pitch
            currentPitch = Mathf.LerpAngle(currentPitch, targetPitch, Time.deltaTime * smoothRotationSpeed);
        }

        UpdateMovement();

        UpdateCollision();

        // Clear this flag
        LockCameraBehindTarget = false;
        RotateCameraBehindTarget = false;
    }


    private void UpdateMovement()
    {
        //根据相机与目标点的旋转角度来计算偏移值
        // Rotate offset vector
        offset = Quaternion.Euler(currentPitch, currentYaw, 0f) * offset;

        // Position camera holder correctly
        transform.position = TargetPosition + offset;

        // And then have the camera look at our target
        Camera.transform.LookAt(TargetPosition);
    }


    private void UpdateCollision()
    {
        // Make sure we don't collide with anything
        collisionClosest = float.MaxValue;
        bool mid = AvoidCollision(transform.position);
        bool bottomLeft = AvoidCollision(Camera.ScreenToWorldPoint(new Vector3(0, 0, Camera.nearClipPlane)));
        bool bottomRight = AvoidCollision(Camera.ScreenToWorldPoint(new Vector3(0, Screen.height, Camera.nearClipPlane)));
        bool topLeft = AvoidCollision(Camera.ScreenToWorldPoint(new Vector3(Screen.width, 0, Camera.nearClipPlane)));
        bool topRight = AvoidCollision(Camera.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.nearClipPlane)));

        if (mid && bottomLeft && bottomRight && topLeft && topRight)
        {
            currentMinDistance = MinDistance;
            currentMaxDistance = MaxDistance;
        }
        else
        {
            currentMinDistance = Mathf.Min(currentMinDistance, 0.2f);
            currentMaxDistance = Mathf.Max(currentMinDistance + 0.05f, collisionClosest * 0.5f);
        }
    }


    private bool AvoidCollision(Vector3 point)
    {
        RaycastHit hit;
        //目标向顶点发射射线
        Vector3 direction = (point - TargetPosition).normalized;

        if (Physics.Raycast(TargetPosition, direction, out hit, MaxDistance, Obstacles))
        {
            Debug.Log(hit.transform.name);
            float calculatedDistance = (hit.point - Target.position).magnitude;

            if (calculatedDistance < collisionClosest)
            {
                collisionClosest = calculatedDistance;
            }

            return false;
        }

        return true;
    }


    void OnDrawGizmos()
    {
        if (DisplayDebugGizmos && HasTarget)
        {
            Gizmos.color = Color.green;
            Gizmos.DrawWireSphere(TargetPosition, currentMinDistance);

            Gizmos.color = Color.blue;
            Gizmos.DrawWireSphere(TargetPosition, currentMaxDistance);

            Gizmos.color = Color.magenta;
            Gizmos.DrawLine(TargetPosition, Camera.ScreenToWorldPoint(new Vector3(0, 0, Camera.nearClipPlane)));
            Gizmos.DrawLine(TargetPosition, Camera.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.nearClipPlane)));
            Gizmos.DrawLine(TargetPosition, Camera.ScreenToWorldPoint(new Vector3(Screen.width, 0, Camera.nearClipPlane)));
            Gizmos.DrawLine(TargetPosition, Camera.ScreenToWorldPoint(new Vector3(0, Screen.height, Camera.nearClipPlane)));
            Gizmos.DrawLine(TargetPosition, transform.position);

            Gizmos.color = Color.red;
            //Gizmos.color = Color.yellow;
            Gizmos.DrawLine(Vector3.zero, offset.normalized);
            //Gizmos.color = Color.yellow;
            Gizmos.DrawLine(Vector3.zero, -Target.transform.forward);
            //Gizmos.color = Color.gray;
            Gizmos.DrawLine(Vector3.zero, Vector3.up);

        }
    }

    //Mathf.Atan2：返回点X与原点连线的夹角（弧度）
    public static float SignedAngle(Vector3 v1, Vector3 v2, Vector3 n)
    {
        return Mathf.Atan2(Vector3.Dot(n, Vector3.Cross(v1, v2)), Vector3.Dot(v1, v2)) * 57.29578f;
    }
}